package com.alibaba.nacos.api.remote;

import com.alibaba.nacos.api.remote.response.Response;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.commons.collections4.functors.IfClosure;

import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

@Getter
@Setter
@Accessors(chain = true)
public class DefaultRequestFuture
        implements RequestFuture {

    private long timeStamp;
    private volatile boolean done = false;
    private boolean success = false;
    private RequestCallBack requestCallBack;
    private Exception exception;
    private String requestId;
    private String connectionId;
    private Response response;
    private ScheduledFuture timeoutFuture;
    private TimeoutInnerTrigger timeoutInnerTrigger;

    public DefaultRequestFuture setRequestCallBack(RequestCallBack requestCallBack) {
        this.requestCallBack = requestCallBack;
        if (requestCallBack != null) {
            this.timeoutFuture = RpcScheduledExecutor.TIMEOUT_SCHEDULER.schedule(new TimeoutHandler(), requestCallBack.getTimeout(), TimeUnit.MILLISECONDS);
        }
        return this;
    }

    class TimeoutHandler
            implements Runnable {

        public TimeoutHandler() {
        }

        @Override
        public void run() {
            setFailResult(new TimeoutException("Timeout After " + requestCallBack.getTimeout() + " milliseconds, requestId=" + requestId + ", connectionId=" + connectionId));
            if (timeoutInnerTrigger != null) {
                timeoutInnerTrigger.triggerOnTimeout();
            }
        }
    }

    @Override
    public Response get() throws Exception {
        return null;
    }

    @Override
    public Response get(long timeout) throws Exception {
        return null;
    }

    public interface TimeoutInnerTrigger {
        void triggerOnTimeout();
    }

    public void setFailResult(Exception e) {
        this.done = true;
        this.success = false;
        this.exception = e;
        synchronized (this) {
            notifyAll();
        }

        callBacInvoke();
    }

    public void setResponse(Response response) {
        this.done = true;
        this.response = response;
        this.success = true;

        if (this.timeoutFuture != null) {
            timeoutFuture.cancel(true);
        }

        synchronized (this) {
            notifyAll();
        }

        callBacInvoke();
    }

    private void callBacInvoke() {
        if (requestCallBack == null) {
            return;
        }

        Executor executor = requestCallBack.getExecutor();
        executor = Optional.ofNullable(executor).orElse(MoreExecutors.directExecutor());
        executor.execute(() -> {
            if (exception == null) {
                requestCallBack.onResponse(response);
            } else {
                requestCallBack.onException(exception);
            }
        });
    }
}
